﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using CoreEx.Common.Extensions;
using CoreEx.Common.Linq;
using CoreEx.Common.Validation;
using LinFu.IoC.Configuration;

namespace CoreEx.Common.Implementation.Validation
{
    /// <summary>
    /// Determines the properties that a validation expression should be associated with.
    /// </summary>
    [Implements(typeof(ITargetPropertyResolver))]
    public class TargetPropertyResolver : ExpressionVisitor, ITargetPropertyResolver
    {
        private readonly IList<PropertyInfo> _targetProperties = new List<PropertyInfo>();

        private Type _validationTargetType;
        
        /// <summary>
        /// Tries to determine the target property or properties that the validation <paramref name="expression"/> applies to.
        /// </summary>
        /// <param name="expression">The validation expression</param>
        /// <returns>An <see cref="IEnumerable{T}"/> that contains the target properties.</returns>
        public IEnumerable<PropertyInfo> ResolveFrom(Expression expression)
        {
            SetValidationTargetType(expression);
            
            Visit(expression);
            
            return _targetProperties;
        }

        
        /// <summary>
        /// Determines if the <paramref name="m"/> is considered a target property for the validation expression.
        /// </summary>
        /// <param name="m">The currently accessed member.</param>
        /// <returns><see cref="Expression"/></returns>
        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (IsTargetProperty(m))            
                RegisterMemberAsExpressionTarget(m);                
            
            return base.VisitMemberAccess(m);
        }

        /// <summary>
        /// Sets the validation target types based on the validation expression
        /// </summary>
        /// <param name="expression">The validation expression.</param>
        private void SetValidationTargetType(Expression expression)
        {
            _validationTargetType = ((LambdaExpression)expression).Parameters[0].Type;
        }

        /// <summary>
        /// Determines if the <paramref name="memberExpression"/> is a property declared by the target type.
        /// </summary>
        /// <param name="memberExpression">The <see cref="MemberExpression"/> that current is being visited.</param>
        /// <returns><b>True</b> if the <paramref name="memberExpression"/> is a property and is declared by the target type.</returns>
        private bool IsTargetProperty(MemberExpression memberExpression)
        {
            return (memberExpression.Member.DeclaringType == _validationTargetType) && memberExpression.Member is PropertyInfo;
        }
        
        private void RegisterMemberAsExpressionTarget(MemberExpression memberExpression)
        {            
            _targetProperties.Add((PropertyInfo)memberExpression.Member);            
        }        
    }
}
